package com.linked.usermanage.login.controller;


import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.linked.commonentity.basemanage.generallog.LinkedGeneralLog;
import com.linked.commonentity.basemanage.generallog.LinkedProject;
import com.linked.commonentity.usermanage.log.LoginLogEnum;
import com.linked.universal.annotation.BeanTrim;
import com.linked.universal.common.*;
import com.linked.universal.linkedutil.HashUtil;
import com.linked.universal.linkedutil.LinkedRedisKeys;
import com.linked.universal.linkedutil.RSAUtil;
import com.linked.usermanage.config.SecuritySetting;
import com.linked.usermanage.feign.IBaseManageFeign;
import com.linked.usermanage.login.bean.po.LoginLogPO;
import com.linked.usermanage.login.bean.po.LoginPO;
import com.linked.usermanage.login.bean.request.LoginRequest;
import com.linked.usermanage.login.bean.request.PassWordUpdateRequest;
import com.linked.usermanage.login.service.ILoginLogService;
import com.linked.usermanage.login.service.ILoginService;
import com.linked.usermanage.user.service.IUserHandleService;
import com.linked.usermanage.util.IPUtil;
import com.linked.usermanage.util.RandomValidateCodeUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.crypto.bcrypt.BCrypt;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;

/**
 * @author :dbq
 * @date : 2022/9/23 16:48
 */
@RestController
@RequestMapping("/login")
public class LoginController {

    private final static Logger LOGGER = LoggerFactory.getLogger(LoginController.class);

    private final ILoginService loginService;

    private final ILoginLogService loginLogService;

    private final RedisTemplate redisTemplate;

    private final SecuritySetting securitySetting;

    private final ObjectMapper mapper;

    private final IUserHandleService userHandleService;

    private final static String VERIFYCODEKEY = "VerifyCode";

    private final IBaseManageFeign baseManageFeign;

    @Autowired
    public LoginController(ILoginService loginService, ILoginLogService loginLogService, RedisTemplate redisTemplate, SecuritySetting securitySetting, ObjectMapper mapper, IUserHandleService userHandleService, IBaseManageFeign baseManageFeign) {
        this.loginService = loginService;
        this.loginLogService = loginLogService;
        this.redisTemplate = redisTemplate;
        this.securitySetting = securitySetting;
        this.mapper = mapper;
        this.userHandleService = userHandleService;
        this.baseManageFeign = baseManageFeign;
    }

    /**
     * 登录接口
     */
    @PostMapping("/doLogin")
    public Result doLogin(@RequestBody LoginRequest param, HttpServletRequest request) throws JsonProcessingException {
        if (LOGGER.isInfoEnabled()) {
            LOGGER.info(param.getUsername() + "登录！{}", mapper.writeValueAsString(param));
        }

        /**
         * 一、查询登录信息
         * */
        LoginPO loginInfo = null;
        try {
            loginInfo = loginService.queryPassword(param.getUsername());
        } catch (Exception e) {
            LOGGER.error("查询登录信息异常！", e);
            return Result.error(LinkedPrompt.ERROR_MESSAGE);
        }
        /**
         * 二、校验验证码
         * */
        String ip = IPUtil.getIpAddr(request) + ":" + request.getLocalPort();

        if (redisTemplate.opsForHash().hasKey(VERIFYCODEKEY, ip)) {
            String verifyCode = (String) redisTemplate.opsForHash().get(VERIFYCODEKEY, ip);
            if (verifyCode != null) {
                verifyCode = verifyCode.toLowerCase();
                param.setVerifyCode(param.getVerifyCode().toLowerCase());
                if (!verifyCode.equals(param.getVerifyCode())) {
                    return Result.ok(false, "验证码输入错误！");
                } else {
                    redisTemplate.opsForHash().delete(VERIFYCODEKEY, ip);
                }
            } else {
                return Result.ok(false, "未输入验证码！");
            }
        } else {
            return Result.ok(false, "未输入验证码！");
        }
        /**
         * 三、验证密码
         * */
        boolean checkPassword = BCrypt.checkpw(param.getPassword(), loginInfo.getPassword());
        if (!checkPassword) {
            return Result.ok(false, "密码验证失败！");
        }
        /**
         * 四、生成token
         * */
        String finalToken = null;
        String userId = null;
        try {
            userId = userHandleService.queryUserIdByLoginId(loginInfo.getLoginId());
            //加签名
//            String token = RSAUtil.sign(mapper.writeValueAsBytes(linkedToken), securitySetting.getPrivateKey());
//            linkedToken.setToken(token);
//            finalToken = mapper.writeValueAsString(linkedToken);
//            if (LOGGER.isInfoEnabled()) {
//                LOGGER.info("finalToken:" + finalToken);
//            }
//            finalToken = HashUtil.encryptBASE64(finalToken.getBytes(StandardCharsets.UTF_8));
            finalToken = createToken(new LinkedToken(userId));
        } catch (Exception e) {
            LOGGER.error("登录验签失败", e);
            return Result.error("签名失败");
        }
        //token过期时效
        if (baseManageFeign.findSwitchStatus("IF_LOGIN_INFO_REDIS")) {
            redisTemplate.opsForValue().set(LinkedRedisKeys.REDISKEY_TOKEN + userId, null, 1, TimeUnit.HOURS);
        }
        /**
         * 最后、插入日志
         * */
        try {
            loginLogService.saveLoginLog(LoginLogPO.Builder()
                    .withUserId(userId)
                    .withUsername(param.getUsername())
                    .withRemarks("登录成功")
                    .withIPAddr(ip)
                    .withLogType(LoginLogEnum.LOGIN));
            baseManageFeign.insertGeneralLog(
                    LinkedGeneralLog.Builder()
                            .withTraceId(userId)
                            .withLogPosition("/login/doLogin")
                            .withMethodParam(param.getUsername())
                            .withPositionName("登录")
                            .withLogProject(LinkedProject.Linked_UserManage)
                            .withLogOrder(1));
        } catch (Exception e) {
        }
        return Result.success(finalToken);
    }

    @PostMapping("createToken")
    String createToken(LinkedToken linkedToken) throws Exception {
        /**
         * 用私钥对用户名和过期时间进行签名
         * */
        String token = RSAUtil.sign(mapper.writeValueAsBytes(linkedToken), securitySetting.getPrivateKey());
        linkedToken.setToken(token);
        String finalToken = mapper.writeValueAsString(linkedToken);
        if (LOGGER.isInfoEnabled()) {
            LOGGER.info("finalToken:" + finalToken);
        }
        /**
         * 对整个token实体类进行base64加密
         * */
        return HashUtil.encryptBASE64(finalToken.getBytes(StandardCharsets.UTF_8));
    }

    @PostMapping("/saveLogin")
    public Result saveLogin(@RequestBody LoginPO loginPO) {

        LOGGER.info("保存" + loginPO.getUsername() + "用户");
        boolean ret = false;
        try {
            boolean isUsed = loginService.checkUsernameUsed(loginPO.getUsername());
            if (isUsed) {
                LOGGER.info("用户名称已存在！");
                return Result.ok(false, "用户名称已存在！");
            }
            ret = loginService.saveLoginInfo(loginPO);
        } catch (Exception e) {
            LOGGER.error("保存登录信息异常！{}", e);
            return Result.error(LinkedPrompt.ERROR_MESSAGE);
        }
        return ret ? Result.ok(true, "保存成功！") : Result.ok(false, "保存失败！");
    }

    @GetMapping("getVerifyCodeImage")
    public void getVerifyCodeImage(HttpServletRequest request, HttpServletResponse response) throws IOException {

        /**
         * 1、生成验证码
         * */
        String verifyCode = RandomValidateCodeUtil.createVerifyCode(4);
        LOGGER.info("验证码为：" + verifyCode);
        /**
         * 2、根据验证码，生成图片
         * */
        BufferedImage image = RandomValidateCodeUtil.createVerifyCodeImage(verifyCode);
        /**
         * 3、放入到缓存中
         * */
        String ip = IPUtil.getIpAddr(request);
        ip += ":" + request.getLocalPort();
        redisTemplate.opsForHash().put(VERIFYCODEKEY, ip, verifyCode);
        redisTemplate.opsForHash().getOperations().expire(VERIFYCODEKEY, 60, TimeUnit.SECONDS);
        response.setContentType("image/png");
        OutputStream os = response.getOutputStream();
        ImageIO.write(image, "png", os);
    }

    @PostMapping("/updatePassWord")
    @BeanTrim
    LinkedResult updatePassWord(PassWordUpdateRequest param, HttpServletRequest request) {
        if (LOGGER.isInfoEnabled()) {
            LOGGER.info("修改密码接口 ----- 入参：" + param.getLoginName());
        }
        if (StringUtils.isEmpty(param.getNewPassWord()) && param.getNewPassWord().equals(param.getRepartPassWord())) {
            return LinkedResult.Complete(LinkedCEM.TWICE_PASSWORD_DIFFERENT);
        }
        LoginPO loginInfo = null;
        try {
            loginInfo = loginService.findLoginInfo(param.getLoginName());
            if (ObjectUtils.isEmpty(loginInfo)) {
                return LinkedResult.Complete(LinkedCEM.EMPTY_RESULT);
            }

        } catch (Exception e) {
            LOGGER.error("修改密码接口 ----- 异常！{}", e);
            return LinkedResult.Complete(LinkedCEM.ERROR_MESSAGE);
        }
        /**
         * 最后、插入日志
         * */
        String userId = "";
        String ip = IPUtil.getIpAddr(request) + ":" + request.getLocalPort();
        try {
            loginLogService.saveLoginLog(
                    LoginLogPO.Builder()
                            .withUserId(userId)
                            .withUsername(param.getLoginName())
                            .withRemarks("密码修改成功")
                            .withIPAddr(ip)
                            .withLogType(LoginLogEnum.UPDATE_PASSWORD)
            );
            baseManageFeign.insertGeneralLog(
                    LinkedGeneralLog.Builder()
                            .withTraceId(userId)
                            .withLogPosition("/login/updatePassWord")
                            .withMethodParam(param.getLoginName())
                            .withPositionName("修改密码")
                            .withLogProject(LinkedProject.Linked_UserManage)
                            .withLogOrder(1)
            );
        } catch (Exception e) {
        }
        return LinkedResult.Success();
    }

}
